package com.song.user.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.cglib.proxy.*;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.web.ZuulController;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * Zuul 已经停止更新，在使用高版本 spring cloud 和 springboot时，需增加这个配置
 * 另外，因版本问题，依赖导入时也必须指定版本号
 * 推荐使用 spring-cloud-gateway
 */
@Configuration
public class ZuulConfig {
    private static final String ERROR_PATH = "/error";
    private static final String METHOD = "lookupHandler";

    @Bean
    public ZuulPostProcessor zuulPostProcessor(
            @Autowired RouteLocator routeLocator,
            @Autowired ZuulController zuulController,
            @Autowired(required = false) ErrorController errorController
    ){
        return new ZuulPostProcessor(routeLocator, zuulController, errorController);
    }

    private enum LookupHandlerCallbackFilter implements CallbackFilter {
        INSTANCE
        ;

        public int accept(Method method) {
            if (METHOD.equals(method.getName())){
                return 0;
            }
            return 1;
        }
    }

    private enum LookupHandlerMethodInterceptor implements MethodInterceptor {
        INSTANCE
        ;

        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if (ERROR_PATH.equals(objects[0])){
                return null;
            }
            return methodProxy.invokeSuper(o, objects);
        }
    }

    private static final class ZuulPostProcessor implements BeanPostProcessor {
        private final RouteLocator routeLocator;
        private final ZuulController zuulController;
        private final boolean hasErrorController;

        ZuulPostProcessor(RouteLocator routeLocator, ZuulController zuulController, ErrorController errorController){
            this.routeLocator = routeLocator;
            this.zuulController = zuulController;
            this.hasErrorController = (errorController != null);
        }

        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (hasErrorController && bean instanceof ZuulHandlerMapping){
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(ZuulHandlerMapping.class);
                enhancer.setCallbackFilter(LookupHandlerCallbackFilter.INSTANCE);
                enhancer.setCallbacks(new Callback[]{LookupHandlerMethodInterceptor.INSTANCE, NoOp.INSTANCE});
                Constructor<?> constructor = ZuulHandlerMapping.class.getConstructors()[0];
                return enhancer.create(constructor.getParameterTypes(), new Object[]{routeLocator, zuulController});
            }
            return bean;
        }
    }
}
